Passed
Pull Request — master (#173)
by
unknown
01:49
created

file-write.ts ➔ getFileBufferSize   A

Complexity

Conditions 3

Size

Total Lines 5
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 5
rs 10
c 0
b 0
f 0
cc 3
1
import {
2
    fsWritePromise,
3
    processFileSync,
4
    processFileAsync,
5
    fsExistsPromise,
6
    fsWriteFilePromise,
7
    fsRenamePromise,
8
    unlinkIfExistSync,
9
    unlinkIfExist,
10
    fsReadAsync
11
} from "./util-file"
12
import * as fs from 'fs'
13
import { Header } from "./id3-tag"
14
import { WriteOptions } from "./types/write"
15
import { hrtime } from "process"
16
import { Id3TagRemover } from "./file-stream-processor"
17
18
// Must be at least Header.size which is the min size to detect an ID3 header.
19
// Naming it help identifying the code handling it.
20
const RolloverBufferSize = Header.size
21
22
const MinBufferSize = RolloverBufferSize + 1
23
const DefaultFileBufferSize = RolloverBufferSize + 20 * 1024 * 1024
24
25
export function writeId3TagToFileSync(
26
    filepath: string,
27
    id3Tag: Buffer,
28
    options: WriteOptions
29
): void {
30
    if (!fs.existsSync(filepath)) {
31
        fs.writeFileSync(filepath, id3Tag)
32
        return
33
    }
34
    const tempFilepath = makeTempFilepath(filepath)
35
    processFileSync(filepath, 'r', (readFileDescriptor) => {
36
        try {
37
            processFileSync(tempFilepath, 'w', (writeFileDescriptor) => {
38
                fs.writeSync(writeFileDescriptor, id3Tag)
39
                copyFileWithoutId3TagSync(
40
                    readFileDescriptor,
41
                    writeFileDescriptor,
42
                    getFileBufferSize(options)
43
                )
44
            })
45
        } catch(error) {
46
            unlinkIfExistSync(tempFilepath)
47
            throw error
48
        }
49
    })
50
    fs.renameSync(tempFilepath, filepath)
51
}
52
53
export async function writeId3TagToFileAsync(
54
    filepath: string,
55
    id3Tag: Buffer,
56
    options: WriteOptions
57
): Promise<void> {
58
    if (!await fsExistsPromise(filepath)) {
59
        await fsWriteFilePromise(filepath, id3Tag)
60
        return
61
    }
62
    const tempFilepath = makeTempFilepath(filepath)
63
    await processFileAsync(filepath, 'r', async (readFileDescriptor) => {
64
        try {
65
            await processFileAsync(tempFilepath, 'w',
66
                async (writeFileDescriptor) => {
67
                    await fsWritePromise(writeFileDescriptor, id3Tag)
68
                    await copyFileWithoutId3TagAsync(
69
                        readFileDescriptor,
70
                        writeFileDescriptor,
71
                        getFileBufferSize(options)
72
                    )
73
                }
74
            )
75
        } catch(error) {
76
            await unlinkIfExist(tempFilepath)
77
            throw error
78
        }
79
80
    })
81
    await fsRenamePromise(tempFilepath, filepath)
82
}
83
84
function getFileBufferSize(options: WriteOptions) {
85
    return Math.max(
86
        options.fileBufferSize ?? DefaultFileBufferSize,
87
        MinBufferSize
88
    )
89
}
90
91
function makeTempFilepath(filepath: string) {
92
    // A high-resolution time is required to avoid potential conflicts
93
    // when running multiple tests in parallel for example.
94
    // Date.now() resolution is too low.
95
    return `${filepath}.tmp-${hrtime.bigint()}`
96
}
97
98
function copyFileWithoutId3TagSync(
99
    readFileDescriptor: number,
100
    writeFileDescriptor: number,
101
    fileBufferSize: number
102
) {
103
    const remover = new Id3TagRemover(fileBufferSize)
104
    do {
105
        const readBuffer = remover.getReadBuffer()
106
        const sizeRead = fs.readSync(readFileDescriptor, readBuffer)
107
        const { skipBuffer, writeBuffer } = remover.processReadBuffer(sizeRead)
108
        fs.readSync(readFileDescriptor, skipBuffer)
109
        fs.writeSync(writeFileDescriptor, writeBuffer)
110
    } while(remover.continue)
111
}
112
113
async function copyFileWithoutId3TagAsync(
114
    readFileDescriptor: number,
115
    writeFileDescriptor: number,
116
    fileBufferSize: number
117
) {
118
    const remover = new Id3TagRemover(fileBufferSize)
119
    do {
120
        const readBuffer = remover.getReadBuffer()
121
        const sizeRead = await fsReadAsync(readFileDescriptor, readBuffer)
122
        const { skipBuffer, writeBuffer } = remover.processReadBuffer(sizeRead)
123
        await fsReadAsync(readFileDescriptor, skipBuffer)
124
        await fsWriteFilePromise(writeFileDescriptor, writeBuffer)
125
    } while(remover.continue)
126
}
127